﻿/*
** NHIN Direct Agent PSEUDOCODE
** IPR Policy at http://nhindirect.org/IPR+Policy
**
** History:    2010-05-02, Microsoft Team, Created first cut
*/

using System;
using System.Security.Cryptography.X509Certificates;

public class TrustAnchor
{
    String thumbprint;
    Boolean sendTo;
    Boolean receiveFrom;
}

public class NHINDErr
{
    // codes and messages, etc.....
}

public class NHINDAgent
{
    // external methods
    public NHINDErr ProcessOutgoingMessage(String inMessage, out String outMessage)
    {
        // extract message sender and receivers from rfc822 headers
        // failures here are fatal
        String fromAddress = ExtractFromHeader(inMessage);
        String toAddressList = ExtractToAddresses(inMessage);

        // Get LOCAL trust configuration from abstract implementation 
        // failures here are fatal
        TrustAnchor[] trustAnchorList = this.GetTrustAnchors(fromAddress);
        X509Certificate[] privateCertList = this.GetPrivateCertificates(fromAddress);

        // Sign the message with each of our private certificates
        // failures here are fatal
        foreach (X509Certificate privateCert in privateCertList)
            SignOutMessage(privateCert);

        // Encrypt the message with a symmetric key (standard S/MIME)
        // failures here are fatal
        String randomKey = EncryptOutMessageWithNewRandomKey();

        // Encrypt key for each recipient (toAddress), verifying "send" trust relationship
        // failures here are partial --- the returned error structure will have a report
        // for each address so the caller knows which were sent and which were not ...
        // standard email semantics here
        foreach (String toAddress in toAddressList)
        {
            // get REMOTE trust configuration from abstract implementation. note the remote
            // lookup may return more than one certificate to support rollover (there must be
            // overlap between adding a new certificate and removing an old one).
            // failures here are fatal for this receipient only
            X509Certificate publicCert = this.GetPublicCertificates(toAddress)[0];

            // verify that we are ok sending to this address
            // failures here are fatal for this recipient only
            VerifyCertExistsInSendTrustAnchors(publicCert, trustAnchorList);

            // encrypt the key for this recipient
            // failures here are fatal ofr this recipient only
            EncryptMessageKeyForRecipient(publicCert, randomKey);
        }

        // done! outMessage is now prepped for delivery over arbitrary transport.
        // caller must review NHINDErr structure to ensure which addresses were acceptable
    }

    public NHINDErr ProcessIncomingMessage(String inMessage, String toAddress, out String outMessage)
    {
        // verify that inMessage is an encrypted message (content type = applciation-pkcs7-mime)
        // failures here are fatal
        VerifyEncryptedMessage(inMessage);

        // extract message sender from rfc822 headers (note the caller is required to pass in
        // the specific toAddress because there may be multiples in hte rfc822 header)
        // failures here are fatal
        String fromAddress = ExtractFromHeader(inMessage);

        // Get LOCAL trust configuration from abstract implementation 
        // failures here are fatal
        TrustAnchor[] trustAnchorList = this.GetTrustAnchors(toAddress);
        X509Certificate[] privateCertList = this.GetPrivateCertificates(toAddress);

        // Decrypt the message using the appropriate privateCert (because of rollover, we need to expect
        // that messages will come in signed using any of the certs in the list)
        // failures here are fatal
        DecryptMessage(privateCertList, inMessage);

        // Get REMOTE trust configuration from abstract implementation 
        // IMPORTANT! We use this online fetch of certificates so that we do not have to deal with CRL 
        // management ... a HISP should not return public certificates that have been revoked.
        // However, failures here can be treated as NON-FATAL ... transient failures are relatively likely due
        // to connectivity issues, and failing legitimate messages here is somewhat awkward since time may have 
        // passed since the message was actually sent. We support this situation by continuing, relying on the 
        // certificates embedded within the standard S/MIME signatures, and communicate this to the caller using
        // a success return code of VALIDATED_OFFLINE that the caller can review and expose appropriately. 
        // Note it is acceptable for a specific implementation to avoid this complexity and just fail, with the
        // understanding that some legitimate messages may get bounced back to the sender during times of
        // network instability.
        X509Certificate[] publicCertList = this.GetPublicCertificates(fromAddress);
        Boolean validatingOffline = /* call to GetPublicCertificate failed for network issues */ ;

        Boolean validatedTrustedSignature = false;
        foreach (signature in inMessage)
        {
            // Ensure this is still a valid certificate for fromAddress. Remember we might skip this step
            // if we are validating offline. If we do make this check, failure here results in continuing
            // to the next signature
            if (!validatingOffline && !FindCertificateInList(signature, publicCertList))
                continue;

            // determine if we trust this signature 
            // failure here results in continuing to next signature
            if (!CertExistsInReceiveTrustAnchors(signature, trustAnchorList))
                continue;

            // ensure that subject cert is either the full fromAddress or the health domain name of
            // the fromAddress. This double-check allows us to support the case where a health domain assigns
            // either individual certificates or org-level certificates to its addresses. We will mark
            // the match type in the NHINDErr structure returned from this method, but either is success
            // from our prespective. failures continue to next signature.
            if (!CertSubjectEqualsFromAddress(signature, fromAddress) && 
                !CertSubjectEqualsFromHealthDomain(signature, fromAddress))
                continue;

            // verify that the signature matches the message
            if (!VerifySignature(signature, inMessage))
                continue;

            // if we made it this far --- success!
            validatedTrustedSignature = true;
            break;
        }

        // done! if validatedTrustedSignature is true, we were successful and we return status in NHINDErr and
        // the decrypted message in outMessage.
    }

    // implemented by the certificate store
    protected abstract NHINDErr GetTrustAnchors(String address,
                                                out TrustAnchor[] anchors);

    protected abstract NHINDErr GetPublicCertificates(String address,
                                                      out X509Certificate[] certs);

    protected abstract NHINDErr GetPrivateCertificates(String address,
                                                       out X509Certificate[] certs);
}
